package com.duoec.dw.lineage.dto;

import com.duoec.dw.lineage.core.utils.AnalyserUtils;
import com.duoec.dw.lineage.exceptions.LineageAnalyseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 多表依赖血缘关系
 *
 * @author xuwenzhen
 */
public class MultiTableLineageDto {
    private static final Logger logger = LoggerFactory.getLogger(MultiTableLineageDto.class);

    public static final String TEMP_TABLE_ALIAS = "__tmp__";

    /**
     * 存放数据
     */
    private LinkedHashSet<TableLineageDto> tables = new LinkedHashSet<>();

    /**
     * table / alias映射
     */
    private Map<String, TableLineageDto> nameMap = new HashMap<>();

    /**
     * 添加一个表依赖
     *
     * @param lineageDto 血缘关系依赖
     */
    public void add(TableLineageDto lineageDto) {
        if (tables.contains(lineageDto)) {
            throw new LineageAnalyseException("已经存在：" + lineageDto.toString());
        }
        String tableName = lineageDto.getTableName();
        String alias = lineageDto.getAlias();
        if (TEMP_TABLE_ALIAS.equals(alias)) {
            tables.add(lineageDto);
            return;
        }

        Boolean lateralView = lineageDto.getLateralView();
        boolean isLateralView = lateralView == null || !lateralView;
        if (tableName != null && isLateralView) {
            TableLineageDto existsTable = nameMap.get(tableName);
            if (existsTable != null && existsTable != lineageDto) {
                //表名相同
                if (existsTable.getAlias() != null && alias != null && !existsTable.getAlias().equals(alias)) {
                    //别名不同，这时候应该直接使用别名，不能使用表名！
                    nameMap.put(alias, lineageDto);
                    return;
                }
                throw new LineageAnalyseException("已经存在name=" + tableName);
            }
            nameMap.put(tableName, lineageDto);
        }
        if (alias != null) {
            TableLineageDto existsTable = nameMap.get(alias);
            if (existsTable != null && existsTable != lineageDto) {
                throw new LineageAnalyseException("已经存在alias=" + alias);
            }
            nameMap.put(alias, lineageDto);
        }
        tables.add(lineageDto);
    }

    /**
     * 添加多个表依赖
     *
     * @param lineages 血缘关系依赖
     */
    public void add(MultiTableLineageDto lineages) {
        if (CollectionUtils.isEmpty(lineages.tables)) {
            return;
        }
        this.tables.addAll(lineages.tables);
        this.nameMap.putAll(lineages.nameMap);
    }

    /**
     * 获取第一个元素
     *
     * @return 第一个表的血缘关系数据
     */
    public TableLineageDto getFirst() {
        return this.tables.iterator().next();
    }

    /**
     * 通过表名或别名获取
     *
     * @param key 表名或别名
     * @return TableLineageDto实例
     */
    public TableLineageDto get(String key) {
        return nameMap.get(key);
    }

    public LinkedHashSet<TableFieldDependencyDto> getDependencies(String tableName, String fieldName) {
        TableLineageDto tableLineage;
        if (tableName == null) {
            if (tables.size() == 1) {
                tableLineage = getFirst();
            } else {
                //全盘扫描
                LinkedHashSet<TableFieldDependencyDto> dependencies = getDependenciesByFieldName(fieldName);
                if (dependencies == null) {
                    //TODO 如果找不到时，可以尝试加载表字段
                    throw new LineageAnalyseException("无法在当前可用表:" + keysToString() + "中找到字段[" + fieldName + "]，请明确此字段使用的表！");
                }
                return dependencies;
            }
        } else {
            tableLineage = get(tableName);
            if (tableLineage == null) {
                // 尝试从复杂字段体中查找
                tableLineage = getFromFieldStruct(tableName, fieldName);
                if (tableLineage == null) {
                    throw new LineageAnalyseException("无法找到表[" + tableName + "]的血缘关系数据！");
                } else {
                    fieldName = tableName;
                }
            }
        }

        return AnalyserUtils.getFieldDependencies(tableLineage, fieldName);
    }

    private TableLineageDto getFromFieldStruct(String fieldName, String structItemName) {
        Iterator<TableLineageDto> iterator = tables.iterator();
        while (iterator.hasNext()) {
            TableLineageDto table = iterator.next();
            TableFieldLineageDto field = table.getFieldByName(fieldName);
            if (field != null) {
                return table;
            }
        }
        return null;
    }

    private LinkedHashSet<TableFieldDependencyDto> getDependenciesByFieldName(String fieldName) {
        LinkedHashSet<TableFieldDependencyDto> dependencies = new LinkedHashSet<>();
        boolean founded = searchByFieldName(fieldName, (table, field) -> {
            if (table == null) {
                return false;
            }
            if (table.getDependencies() != null) {
                dependencies.addAll(table.getDependencies());
            }
            if (field == null) {
                //如果命中的字段为空，则需要添加
                TableFieldDependencyDto dependency = new TableFieldDependencyDto();
                dependency.setFieldName(fieldName);
                dependency.setDatabaseName(table.getDatabaseName());
                dependency.setTableName(table.getTableName());
                dependencies.add(dependency);
            } else if (field.getDependencies() != null) {
                dependencies.addAll(field.getDependencies());
            }
            return true;
        });

        if (!founded) {
            //没有找到时
            return null;
        }
        return dependencies;
    }

    public <T> T searchByFieldName(String fieldName, OnFieldFound<T> fieldFound) {
        TableLineageDto lineage = null;
        TableFieldLineageDto foundField = null;
        Iterator<TableLineageDto> its = tables.iterator();
        while (its.hasNext()) {
            TableLineageDto dto = its.next();
            TableFieldLineageDto field = dto.getFieldByName(fieldName);
            if (field != null) {
                if (lineage != null) {
                    logger.warn("无法明确哪个表的字段：[{}, {}].{}，随机选择：{}", lineage.getTableName(), dto.getTableName(), fieldName, lineage.getTableName());
                    foundField = field;
                    break;
                } else {
                    lineage = dto;
                    foundField = field;
                }
            }
        }

        if (lineage == null) {
            //尝试检查是否有为虚拟表
            List<TableLineageDto> hasNameTables = tables.stream().filter(table -> table.getTableName() != null).collect(Collectors.toList());
            if (hasNameTables.size() == 1) {
                lineage = hasNameTables.get(0);
            }
        }

        return fieldFound.after(lineage, foundField);
    }

    public int size() {
        return tables.size();
    }

    /**
     * 打印当前所有表的表名
     *
     * @return 表名，多个表使用半角逗号分隔
     */
    public String keysToString() {
        if (nameMap.isEmpty()) {
            return "[]";
        }
        StringBuilder sb = new StringBuilder();
        nameMap.keySet().stream().forEach(key -> {
            if (sb.length() > 0) {
                sb.append(", ");
            }
            sb.append(key);
        });
        return "[" + sb.toString() + "]";
    }

    public void setAlias(String alias) {
        if (tables.size() == 1) {
            TableLineageDto table = getFirst();
            table.setAlias(alias);
            //不需要检查是否一致
            nameMap.put(alias, table);
        }
    }

    public LinkedHashSet<TableLineageDto> getTables() {
        return tables;
    }

    public TableLineageDto getTable(String name) {
        return nameMap.get(name);
    }

    public interface OnFieldFound<T> {
        /**
         * 查询结果返回后执行
         *
         * @param table 查找到的表
         * @param field 查找到的字段
         * @return 处理结果（自定义）
         */
        T after(TableLineageDto table, TableFieldLineageDto field);
    }

    public boolean containsTableName(String tableName) {
        return this.nameMap.containsKey(tableName);
    }
}
